package com.united.ucts.common.plugin.security;

import com.united.frame.common.constant.AuthConstant;
import com.united.frame.plugin.security.GrantTypeEnum;
import com.united.ucts.modules.auth.service.impl.UserServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;

/**
 * 认证服务器配置
 * 根据实际需要配置加载用户信息的服务UserServiceImpl及RSA的钥匙对KeyPair
 *
 * @author hudan
 * @version 1.0
 * @filename Oauth2ServerConfig.java
 * @copyright <a href="www.united.cn">www.united.cn</a>
 * @date 2019-10-10
 */
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private final JwtTokenEnhancer jwtTokenEnhancer;
    private final PasswordEncoder passwordEncoder;
    private final AuthenticationManager authenticationManager;
    private final UserServiceImpl userDetailsService;

    /**
     * 配置从哪里获取ClientDetails信息.
     * 在client_credentials授权方式下,只要这个ClientDetails信息
     * 此方法主要作用是是注入ClientDetailsService实例对象(唯一配置注入).
     * 其它地方可以通过ClientDetailsServiceConfigurer调用开发配置的ClientDetailsService
     *
     * @param clientsDetails 请求客户端对象
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clientsDetails) throws Exception {

        // 将客户端信息存储在内存中
        clientsDetails.inMemory()

                /* 定义访问admin后台的认证授权相关配置信息 */
                .withClient(AuthConstant.ADMIN_CLIENT_ID)
                // 默认登录密码
                .secret(passwordEncoder.encode(AuthConstant.CLIENT_DEFAULT_PASSWORD))
                // 允许的授权范围
                .scopes("all")
                // 该客户端允许的授权类型 (密码、刷新token、授权码)
                .authorizedGrantTypes(GrantTypeEnum.PASSWORD.getCode(), GrantTypeEnum.REFRESH_TOKEN.getCode(), GrantTypeEnum.AUTHORIZATION_CODE.getCode())
                // 配置token的有效时间24小时
                .accessTokenValiditySeconds(3600 * 24)
                // 配置refreshToken的有效时间7*24小时
                .refreshTokenValiditySeconds(3600 * 24 * 7)
                .and()

                /* 定义访问前端客户端的认证授权相关配置信息 */
                .withClient(AuthConstant.PORTAL_CLIENT_ID)
                .secret(passwordEncoder.encode(AuthConstant.CLIENT_DEFAULT_PASSWORD))
                .scopes("all")
                .authorizedGrantTypes(GrantTypeEnum.PASSWORD.getCode(), GrantTypeEnum.REFRESH_TOKEN.getCode(), GrantTypeEnum.AUTHORIZATION_CODE.getCode())
                .accessTokenValiditySeconds(3600 * 24)
                .refreshTokenValiditySeconds(3600 * 24 * 7);
    }

    /**
     * 访问端点配置
     * 注入 tokenStroe、tokenEnhancer相关服务 配置：
     * 1. 密码模式下配置认证管理器 AuthenticationManager
     * 2. 设置 AccessToken的存储介质tokenStore, 默认使用内存当做存储介质.
     * 3. userDetailsService注入
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();

        // 自定义的JWT内容增强器,用于向token里写入客户端类型信息
        delegates.add(jwtTokenEnhancer);
        delegates.add(accessTokenConverter());

        // 配置JWT的内容增强器
        enhancerChain.setTokenEnhancers(delegates);

        // 配置访问端点
        endpoints.authenticationManager(authenticationManager)
                // 配置加载用户信息的服务(MUST：密码模式下需设置一个AuthenticationManager对象,获取 UserDetails信息)
                .userDetailsService(userDetailsService)
                // 定义token的生成方式
                .accessTokenConverter(accessTokenConverter())
                // token 里增加一些增强内容
                .tokenEnhancer(enhancerChain);
    }

    /**
     * 访问安全配置
     * 定义安全检查流程,用来配置令牌端点（Token Endpoint）的安全与权限访问
     * 默认过滤器：BasicAuthenticationFilter
     * 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
     * 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值：denyAll()
     * 对以下的几个端点进行权限配置：
     * /oauth/authorize：授权端点
     * /oauth/token：令牌端点
     * /oauth/confirm_access：用户确认授权提交端点
     * /oauth/error：授权服务错误信息端点
     * /oauth/check_token：用于资源服务访问的令牌解析端点
     * /oauth/token_key：提供公有密匙的端点,如果使用JWT令牌的话
     **/
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        // 允许表单认证.针对/oauth/token端点
        security.allowFormAuthenticationForClients();
//                // 设置oauth_client_details中的密码编码器
//                .passwordEncoder(new BCryptPasswordEncoder())
//                .tokenKeyAccess("permitAll()")
//                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * token生成处理
     * 将生成的token按照定义的rsa加签方式进行加签
     *
     * @return token的生成方式
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }

    /**
     * 秘钥对生成器
     *
     * @return 秘钥对生成器
     */
    @Bean
    public KeyPair keyPair() {
        // 从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"),
                AuthConstant.CLIENT_DEFAULT_PASSWORD.toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", AuthConstant.CLIENT_DEFAULT_PASSWORD.toCharArray());
    }
}